package com.beiding.hotcode;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class RealInstance extends MyInstance {

    private Class type;

    private MyClass myClass;


    RealInstance(MyClass myClass, Class type, Object... params) {
        this.myClass = myClass;
        this.type = type;
        newInstance(params);
    }

    RealInstance() {
    }

    @Override
    public Class getType() {
        return type;
    }

    @Override
    public MyClass getMyClass() {
        return myClass;
    }

    @Override
    public Object getInstance() {
        return instance;
    }

    Object instance;

    private void makeMethodParamMap() {
        Class<?> aClass = instance.getClass();
        for (Method method : aClass.getMethods()) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            Map<Integer, List<MethodHolder>> integerListMap = methods.computeIfAbsent(method.getName(), k -> new HashMap<>());
            List<MethodHolder> methodHolders = integerListMap.computeIfAbsent(parameterTypes.length, k -> new ArrayList<>());
            MethodHolder methodHolder = new MethodHolder(method, parameterTypes);
            methodHolders.add(methodHolder);
        }

    }

    private void newInstance(Object... params) {
        try {

            Constructor[] constructors = type.getConstructors();

            Constructor constructor = null;

            out:
            for (Constructor c : constructors) {
                Class[] parameterTypes = c.getParameterTypes();
                if (parameterTypes.length != params.length) {
                    continue;
                }
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (params[i] == null) {
                        continue;
                    }

                    if (!parameterTypes[i].isInstance(params[i])) {
                        continue out;
                    }

                }
                if (constructor != null) {
                    throw new RuntimeException("无法确定构造函数:" + type.getName() + "(" + createParamTypesString(params) + ")");
                }
                constructor = c;
            }
            if (constructor == null) {
                throw new RuntimeException("未找到构造函数:" + type.getName() + "(" + createParamTypesString(params) + ")");
            }

            instanceLifecycle.onBeforeAbandon(this);

            instance = constructor.newInstance(params);
            makeMethodParamMap();
            instanceLifecycle.onAfterCreate(this);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Method getMethod(String method, Object... params) {
        Map<Integer, List<MethodHolder>> integerListMap = methods.get(method);
        if (integerListMap == null) {
            return null;
        }
        List<MethodHolder> methodHolders = integerListMap.get(params.length);
        if (methodHolders == null) {
            return null;
        }

        Method m = null;
        for (MethodHolder methodHolder : methodHolders) {
            if (methodHolder.match(params)) {
                if (m != null) {
                    throw new RuntimeException("无法确定方法:" + method + "(" + createParamTypesString(params) + ")");
                }
                m = methodHolder.getMethod();
            }
        }

        if (m == null) {
            throw new RuntimeException("未找到方法:" + method + "(" + createParamTypesString(params) + ")");
        }

        return m;
    }

    //发起调用操作
    public Object invokeCatch(String method, Object... params) throws Throwable {
        try {
            Method m = getMethod(method, params);
            if (m == null) {
                throw new NoSuchMethodException(method + "(" + createParamTypesString(params) + ")");
            }
            return m.invoke(instance, params);
        } catch (InvocationTargetException e) {

            //抛出所有异常
            throw e.getTargetException();

        } catch (Exception e) {

            throw new RuntimeException(e);
        }
    }

    public void set(String field, Object value) {
        try {
            Field f = instance.getClass().getField(field);
            f.set(instance, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void forceSet(String field, Object value) {
        try {
            Field f = instance.getClass().getDeclaredField(field);
            f.setAccessible(true);
            f.set(instance, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object get(String field) {
        try {
            Field f = instance.getClass().getDeclaredField(field);
            return f.get(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object forceGet(String field) {
        try {
            Field f = instance.getClass().getField(field);
            f.setAccessible(true);
            return f.get(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> MyMethod<T> method(String name, Class<T> type) {
        return params -> (T) RealInstance.this.invoke(name, params);
    }


    private String createParamTypesString(Object[] params) {

        StringBuilder builder = new StringBuilder();

        if (params.length > 0) {
            builder.append(params[0] == null ? "null" : params[0].getClass().getName());
            for (int i = 1; i < params.length; i++) {
                builder.append(",").append(params[i] == null ? "null" : params[i].getClass().getName());
            }
        }

        return builder.toString();

    }
}
